function ForceLayout(config){
	const {
		nodes,
		links,
		width,
		height,
		svg,
		defs,
		menus,
	} = config;
	this.nodes = nodes;
	this.links = links;
	this.width = width;
	this.height = height;
	this.svg = svg;
	this.defs = defs;
	this.menus = menus;
	
	this.simulation = d3.forceSimulation(this.nodes)
		.force("link",d3.forceLink(this.links).id(d=>d.id))
		.force("charge",d3.forceManyBody().strength(-300))
		.force("center",d3.forceCenter(width / 2, height / 2))
		.force("collide", d3.forceCollide(100).strength(0.2).iterations(5))
		.alphaDecay(0.0228);
	
	this.holderDom = this.svg.append("g")
		.attr("class","holders")
		.attr("width",this.width)
		.attr("height",this.height);
		
	this.nodeDom = this.holderDom
		.selectAll("circle.circleclass")
		.data(this.nodes)
		.enter()
		.append("circle")
		.attr("class","circleclass")
		.attr("fill",d=>("url(#ava"+d.id+")"))
		.attr("r",d=>d.r)
		.attr("nodeid",d=>d.id)
		.attr("stroke","#ccc")
		.attr("stroke-width",2);
		
		this.edgeDom = this.holderDom
			.selectAll("g.edge")
			.data(this.links)
			.enter()
			.append("g")
			.attr("class","edge")
			.attr("fill","#bad4ed");
		
		this.edgeLink = this.edgeDom
			.append("path")
			.attr("class","links")
			.style("marker-end", "url(#marker)")
			.attr("stroke","#bad4ed");
}

ForceLayout.prototype.init = function(){
	const _this = this;
	this.svg 
	.attr("width",this.width)
	.attr("height",this.height)
	.call(d3.zoom().scaleExtent([0.5, 1.5]).on("zoom",()=>{
			this.holderDom.attr("transform", d3.event.transform);
		})
	).on("click",()=>{
		this.menus.style("display","none");
	});
	this.defs
		.append("marker")
		.attr("id","marker")
		.attr("markerWidth",10)
		.attr("markerHeight",10)
		.attr("refX",36)
		.attr("refY",4)
		.attr("orient","auto")
		.attr("markerUnits","userSpaceOnUse")
		.append("path")
		.attr("d","M 0 0 8 4 0 8Z")
		.attr("fill", "steelblue");
		
	const patterns = 
		this.defs
		.selectAll("pattern.patternclass")
		.data(this.nodes)
		.enter()
		.append("pattern")
		.attr("class","patternclass")
		.attr("id",d=>"ava"+d.id)
		.attr('patternUnits', 'objectBoundingBox')
		.attr("x", "0")
		.attr("y", "0")
		.attr("width", "1")
		.attr("height", "1");
		
	patterns.append("image")
		.attr("xlink:href",d=>d.pic)
		.attr("src",d=>d.pic)
		.attr("width",d=>d.r*2)
		.attr("height",d=>d.r*2)
		.attr("preserveAspectRatio", "xMidYMin slice");
		
	this.simulation.on("tick", () => this.ticked());
	
	this.nodeDom
		.on("click",()=>{
			
		})
		.on("mouseover",function(){
			d3.select(this)
			.attr("stroke-width",6)
			.attr("stroke",'#c5dbf0');
		})
		.on("mouseout",function(){
			d3.select(this)
			.attr("stroke-width",2)
			.attr("stroke",'#ccc');
		})
		.on("contextmenu",function(){
			event = event || window.event;
			event.cancelBubble = true;
			event.returnValue = false;
			d3.event.preventDefault();//屏蔽右键菜单

			_this.menus.style("left",d3.select(this).attr("cx")+"px")
				.style("top",d3.select(this).attr("cy")+"px")
				.style("display","block");
			
			_this.menus.attr("parentid",d3.select(this).attr("nodeid"));
			
		})
		.call(
			d3.drag()
			.on("start",d=>this.dragStart(d))
			.on("end",d=>this.dragEnd(d))
			.on("drag",d=>this.draged(d))
		);
}
ForceLayout.prototype.dragStart = function(d){
	d3.event.sourceEvent.stopPropagation();
	if (!d3.event.active) this.simulation.alphaTarget(0.3).restart();
	d.fx = d.x;
    d.fy = d.y;
}
ForceLayout.prototype.dragEnd = function(d){
	if (!d3.event.active) this.simulation.alphaTarget(0);
	d.fx = null;
    d.fy = null;
}
ForceLayout.prototype.draged = function(d){
	d.fx = d3.event.x;
    d.fy = d3.event.y;
}
ForceLayout.prototype.ticked = function(){
	this.nodeDom.attr("cx",d=>d.x)
	.attr("cy",d=>d.y);
	
	this.edgeDom.attr("transform",d=>{
		return this.getTransform(d.source, d.target, this.getDis(d.source, d.target));
	});
	
	 this.edgeLink.attr("d", d => {
            return "M3,0 L"+this.getDis(d.source, d.target)+",0";
     });
}

// 求元素移动到目标位置所需要的 transform 属性值
ForceLayout.prototype.getTransform = function(source, target, _dis){
	var r;
    if (target.x > source.x) {
        if (target.y > source.y) {
            r = Math.asin((target.y - source.y) / _dis)
        } else {
            r = Math.asin((source.y - target.y) / _dis);
            r = -r;
        }
    } else {
        if (target.y > source.y) {
            r = Math.asin((target.y - source.y) / _dis);
            r = Math.PI - r;
        } else {
            r = Math.asin((source.y - target.y) / _dis);
            r -= Math.PI;
        }
    }
    r = r * (180 / Math.PI);
    return "translate(" + source.x + "," + source.y + ")rotate(" + r + ")";
}
ForceLayout.prototype.getDis = function(s, t){
	return Math.sqrt((s.x - t.x) * (s.x - t.x) + (s.y - t.y) * (s.y - t.y));
}

ForceLayout.prototype.addEdges = function(){
	
}
ForceLayout.prototype.addNodes = function(){
	
}
ForceLayout.prototype.addAva = function(){
	
}